{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# OPC python使用说明\n",
    "\n",
    "## 运行环境\n",
    "\n",
    "运行环境 python2.7 + window10 + KEPServerEX.6.4\n",
    "\n",
    "[官方api文档地址](http://openopc.sourceforge.net/api.html)\n",
    "\n",
    "[opc 模拟服务器下载]\n",
    "\n",
    "- https://www.matrikonopc.com/downloads/178/index.aspx\n",
    "\n",
    "- https://pan.baidu.com/s/1R4bC-NKLEl4s4FqRfhAIwQ \n",
    "提取码：07yy\n",
    "\n",
    "## 一、安装 OpenOPC\n",
    "\n",
    "1. [下载 OpenOPC-1.3.1.win32-py2.7.exe](https://sourceforge.net/projects/openopc/files/) 并安装\n",
    "\n",
    "2. pip 安装依赖包\n",
    "\n",
    "```sh\n",
    "pip install Pywin32\n",
    "pip install Pyro\n",
    "```\n",
    "\n",
    "4. 修改环境变量\n",
    ">OPC_MODE = open\n",
    "\n",
    "## 2. api 使用\n",
    "\n",
    "**说明**：鉴于Pyro库版本的原因，本人实际运行的返回值跟文档有些对不上，但是不影响使用👌\n",
    "\n",
    "\n",
    "### 1.  导包\n",
    "\n",
    "**前提：**\n",
    "将 OpenOPC 安装目录下 src 文件夹下的 `OpenOPC.py` 复制到 python2 安装目录下的 `Lib\\site-packages` 目录下，或者你的项目目录下。\n",
    "\n",
    "```py\n",
    ">>> import OpenOPC\n",
    "```\n",
    "\n",
    "### 2. 创建客户端实例\n",
    "\n",
    "#### DCOM 模式\n",
    "\n",
    "DCOM 模式是直连 OPC servers，无需 OpenOPC 网关服务. 但是只允许在 windows上运行。\n",
    "\n",
    "```py\n",
    ">>> opc = OpenOPC.client()\n",
    "```\n",
    "\n",
    "#### open 模式\n",
    "\n",
    "Open 模式下是连接到指定的 [OpenOPC 网关服务](http://openopc.sourceforge.net/service.html)，Windows和Linux 都可以使用。\n",
    "\n",
    "```py\n",
    ">> opc = OpenOPC.open_client()\n",
    "```\n",
    "\n",
    "### 3. 列出本机所有可用的 OPC 服务器\n",
    "\n",
    "```py\n",
    ">>> opc.servers()\n",
    "['Matrikon.OPC.Simulation.1', 'Kepware.KEPServerEX.V6']\n",
    "```\n",
    "### 👍4. 连接服务器\n",
    "\n",
    "连接到指定的服务器节点，如果服务器节点不在本机，需要传入第二个参数：节点的IP地址，默认 localhost\n",
    "\n",
    "连接成功返回`None`\n",
    "\n",
    "```py\n",
    ">>> opc.connect('Matrikon.OPC.Simulation', 'localhost')\n",
    "```\n",
    "\n",
    "### 👍5. 读取 opc 服务器数据\n",
    "\n",
    "#### 读取单个item\n",
    "\n",
    "读取成功返回一个 `(value, quality, timestamp)` 元组，读取失败quality 为`(None, 'Error', None)`\n",
    "\n",
    "```py\n",
    ">>> opc.read('Random.Int4')\n",
    "(19169, 'Good', '06/24/07 15:56:11')\n",
    ">>> value, quality, time = opc.read('Random.Int4')\n",
    "```\n",
    "\n",
    "#### 读取多个Item\n",
    "\n",
    "传入一个标签名列表，返回一个包含一系列元组的列表\n",
    "\n",
    "```py\n",
    ">>> taglist = [u'通道 1.设备 1.标记 1', u'通道 1.设备 1.标记 2']\n",
    ">>> opc.read(taglist)\n",
    "[[\"通道 1.设备 1.标记 1\", 51765, \"Good\", \"06/22/20 13:18:55\"], [\"通道 1.设备 1.标记 2\", 22, \"Good\", \"06/22/20 13:18:55\"]]\n",
    "```\n",
    "**注：** 由于Pyro版本原因，贫僧用的最新的 3.16 版本，在调用 opc.iread()方法时，提示`PicklingError` 错误，这是个以迭代器的方式读取数据方法，资源占用会少一点。\n",
    "\n",
    "```py\n",
    "for name, value, quality, time in opc.iread( ['Random.Int2', 'Random.Int4'] ):\n",
    "   print name, value\n",
    "```\n",
    "\n",
    "### 6. 👍按Group读取OPC数据\n",
    "\n",
    "为了获得最佳性能，通常需要将item放入指定的group中，然后重复请求更新group组的值。  \n",
    "\n",
    "```py\n",
    ">>> tags = ['Random.String', 'Random.Int4', 'Random.Real4']\n",
    "\n",
    ">>> opc.read(tags, group='test')\n",
    "[('Random.String', 'options', 'Good', '06/24/07 23:38:24'), ('Random.Int4', 31101, 'Good', '06/24/07 23:38:24')]\n",
    "```\n",
    "一旦声明了一个组，你就可以直接用`group`参数，重复读取这些组里的item了。\n",
    "\n",
    "```py\n",
    ">>> opc.read(group='test')\n",
    "[('Random.String', 'clients', 'Good', '06/24/07 23:38:30'), ('Random.Int4', 26308, 'Good', '06/24/07 23:38:24')]\n",
    "```\n",
    "\n",
    "当你不想用 group组去访问了，记得移除组标签，这将释放所有已分配的资源。释放成功返回`True`，失败返回`False`\n",
    "\n",
    "```py\n",
    ">>> opc.remove('test')\n",
    "True\n",
    "```\n",
    "**释放所有组对象：**\n",
    "\n",
    "```py\n",
    "opc.remove(opc.groups())\n",
    "```\n",
    "\n",
    "### 7. 写入一个点\n",
    "\n",
    "传入一个 (name, value) 元组即可，成功返回`Success`, 失败返回`Error`\n",
    "\n",
    "#### 方式1\n",
    "\n",
    "```py\n",
    ">>> opc.write( ('Triangle Waves.Real8', 100.0) )\n",
    "'Success'\n",
    "```\n",
    "\n",
    "#### 方式2\n",
    "\n",
    "```py\n",
    ">>> opc['Triangle Waves.Real8'] = 100.0\n",
    "```\n",
    "\n",
    "### 8. 写入多个点\n",
    "\n",
    "传入多个(name, value) 元组组成的列表即可，返回每个点写入的结果，效果如下\n",
    "\n",
    "```py\n",
    ">>> opc.write( [('Triangle Waves.Real4', 10.0), ('Random.String', 20.0)] )\n",
    "[('Triangle Waves.Real4', 'Success'), ('Random.String', 'Error')]\n",
    "```\n",
    "\n",
    "### 👍9. 包含写入结果的提示信息\n",
    "\n",
    "```py\n",
    ">>> opc.write( [('Triangle Waves.Real4', 10.0), ('Random.Int4', 20.0)], include_error=True)\n",
    "[('Triangle Waves.Real4', 'Success', '操作成功完成。 '), ('Random.Int4', 'Error', \"该项目在服务器地址空间中不再可用。\"\n",
    "```\n",
    "\n",
    "### 10. 获取Item属性\n",
    "\n",
    "请求一个Item的所有属性，返回一个元组列表，每一个属性包括`（id、属性名、属性值）`，举例如下：\n",
    "\n",
    "```py\n",
    ">>> opc.properties('Random.Int4')\n",
    "[(1, 'Item Canonical DataType', 'VT_I4'), (2, 'Item Value', 491), (3, 'Item Quality', 'Good'), (4, 'Item Timestamp', '06/25/07 02:24:44'), (5, 'Item Access Rights', 'Read'), (6, 'Server Scan Rate', 100.0), (7, 'Item EU Type', 0), (8, 'Item EUInfo', None), (101, 'Item Description', 'Random value.')]\n",
    "```\n",
    "\n",
    "可选参数 `id` ，直接返回上面返回的属性列表中的对应的单个 id属性。比如，返回id为1的属性值\n",
    "\n",
    "```py\n",
    ">>> opc.properties('Random.Int4', id=1)\n",
    "'VT_I4'\n",
    "```\n",
    "id 参数也可以指定多个，比如返回 Random.Int4 点的 1、2、5属性\n",
    "\n",
    "```py\n",
    ">>> opc.properties('Random.Int4', id=(1,2,5))\n",
    "[(1, 'VT_I4'), (2, 1869), (5, 'Read')]\n",
    "```\n",
    "\n",
    "同样的，也支持批量查询多个Item属性，返回值包括Item名和属性值\n",
    "\n",
    "```py\n",
    ">>> opc.properties( ['Random.Int2', 'Random.Int4', 'Random.String'], id=1)\n",
    "[('Random.Int2', 'VT_I2'), ('Random.Int4', 'VT_I4'), ('Random.String', 'VT_BSTR')]\n",
    "```\n",
    "\n",
    "### 🍎11. 查询可用Item点位\n",
    "\n",
    "查询根节点\n",
    "\n",
    "```py\n",
    ">>> opc.list()\n",
    "['Simulation Items', 'Configured Aliases']\n",
    "```\n",
    "\n",
    "查询指定节点下的子节点\n",
    "\n",
    "```py\n",
    ">>> opc.list('Simulation Items')\n",
    "['Bucket Brigade', 'Random', 'Read Error', 'Saw-toothed Waves', 'Square Waves', 'Triangle Waves']\n",
    "```\n",
    "\n",
    "使用 `.` 连接符进一步查询子节点下可用 点位，比如\n",
    "\n",
    "```py\n",
    ">>> opc.list('Simulation Items.Random')\n",
    "['Random.ArrayOfReal8', 'Random.ArrayOfString', 'Random.Boolean', 'Random.Int1', 'Random.Int2']\n",
    "```\n",
    "\n",
    "\n",
    "使用 `通配符` 查询\n",
    "\n",
    "```py\n",
    ">>> opc.list('Simulation Items.Random.*Real*')\n",
    "['Random.ArrayOfReal8', 'Random.Real4', 'Random.Real8']\n",
    "```\n",
    "\n",
    "使用`通配符` +递归 查询，指定参数 `recursive=True`\n",
    "```py\n",
    ">>> opc.list('Sim*.R*.Real*', recursive=True)\n",
    "['Random.Real4', 'Random.Real8', 'Read Error.Real4', 'Read Error.Real8']\n",
    "```\n",
    "\n",
    "同样地，支持批量查询：\n",
    "\n",
    "```py\n",
    ">>> opc.list(('Simulation Items.Random.*Int*', 'Simulation Items.Random.Real*'))\n",
    "['Random.Int1', 'Random.Int2', 'Random.Int4', 'Random.UInt1', 'Random.UInt2', 'Random.UInt4', 'Random.Real4', 'Random.Real8']\n",
    "```\n",
    "\n",
    "### 👍12. 查询OPC Server信息\n",
    "\n",
    "```py\n",
    ">>> opc.info()\n",
    "[('Protocol', 'OpenOPC'),\n",
    " ('Gateway Host', 'DESKTOP-EFDSAFO:7766'),\n",
    " ('Gateway Version', '1.3.1'),\n",
    " ('Class', 'Graybox.OPC.DAWrapper'),\n",
    " ('Client Name', u'OpenOPC'),\n",
    " ('OPC Host', 'DESKTOP-EFDSAFO'),\n",
    " ('OPC Server', u'Kepware.KEPServerEX.V6'),\n",
    " ('State', 'Running'),\n",
    " ('Version', '6.4 (Build 321)'),\n",
    " ('Browser', 'Hierarchical'),\n",
    " ('Start Time', '06/25/20 05:16:11'),\n",
    " ('Current Time', '06/26/20 07:32:43'),\n",
    " ('Vendor', u'Kepware')]\n",
    "```\n",
    "\n",
    "### 13. 复合查询\n",
    "\n",
    "以上介绍的OpenOPC 方法可以组合使用，从而简化代码量。\n",
    "\n",
    "比如使用模糊查询，并且获取实时值。\n",
    "\n",
    "```py\n",
    ">>> opc.read(opc.list('Simulation Items.Random.*Int*'))\n",
    "[('Random.Int1', 99, 'Good', '06/24/07 22:44:28'), ('Random.Int2', 26299, 'Good', '06/24/07 22:44:28'), ('Random.Int4', 17035, 'Good', '06/24/07 22:44:28'), ('Random.UInt1', 77, 'Good', '06/24/07 22:44:28'), ('Random.UInt2', 28703, 'Good', '06/24/07 22:44:28'), ('Random.UInt4', 23811.0, 'Good', '06/24/07 22:44:28')]\n",
    "```\n",
    "\n",
    "模糊查询所有Real4点位的 1#属性值（日期）\n",
    "\n",
    "```py\n",
    ">>> opc.properties(opc.list('*.Real4', flat=True), id=1)\n",
    "[('Bucket Brigade.Real4', 'VT_R4'), ('Random.Real4', 'VT_R4'), ('Read Error.Real4', 'VT_R4'), ('Saw-toothed Waves.Real4', 'VT_R4'), ('Square Waves.Real4', 'VT_R4'), ('Triangle Waves.Real4', 'VT_R4'), ('Write Error.Real4', 'VT_R4'), ('Write Only.Real4', 'VT_R4')]\n",
    "```\n",
    "\n",
    "**从一个OPC服务器读取数据并写入另一个服务器**：\n",
    "\n",
    "```py\n",
    ">>> opc1.write(opc2.read(opc.list('Simulation Items.Triangle Waves.*Int*')))\n",
    "[('Triangle Waves.Int1', 'Success'), ('Triangle Waves.Int2', 'Success'), ('Triangle Waves.Int4', 'Success'), ('Triangle Waves.UInt1', 'Success'), ('Triangle Waves.UInt2', 'Success'), ('Triangle Waves.UInt4', 'Success')]\n",
    "```\n",
    "\n",
    "### 14. 断开连接\n",
    "\n",
    "```py\n",
    ">>> opc.close()\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 常用Demo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import OpenOPC\n",
    "\n",
    "GROUP_NAME = 'test'\n",
    "opc = OpenOPC.open_client('localhost')\n",
    "# 连接到本地 OPC模拟Server\n",
    "opc.connect('Kepware.KEPServerEX.V6', 'localhost')\n",
    "\n",
    "# 点位列表\n",
    "taglist = [u'通道 1.设备 1.标记 1', u'通道 1.设备 1.标记 2', u'通道 1.设备 1.TAG1']\n",
    "\n",
    "# 创建组对象\n",
    "opc.read(taglist, group=GROUP_NAME)\n",
    "\n",
    "try:\n",
    "    # 循环周期读取\n",
    "    while True:\n",
    "        # 请求组\n",
    "        opc_data = opc.read(group=GROUP_NAME)\n",
    "        # 保存字典\n",
    "        send_values = {}\n",
    "        for item in opc_data:\n",
    "            name, value, quality, time_ = item\n",
    "            if quality == 'Good':\n",
    "                send_values[name] = value\n",
    "            else:\n",
    "                print('Error:  {}'.format(item))\n",
    "        print(send_values)\n",
    "\n",
    "        time.sleep(5)\n",
    "finally:\n",
    "    # 释放资源\n",
    "    opc.remove(opc.groups())\n",
    "    opc.close()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.17"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "216.8px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
